/*
 * Binder de objetos del RM a partir del template, arquetipos y datos submiteados de la web.
 */

package binding


import org.openehr.am.archetype.Archetype
import org.openehr.am.archetype.constraintmodel.ArchetypeConstraint

import org.openehr.am.archetype.assertion.Assertion
import org.openehr.am.archetype.constraintmodel.*
import org.openehr.am.archetype.constraintmodel.primitive.*
import org.openehr.am.openehrprofile.datatypes.quantity.*
import org.openehr.am.openehrprofile.datatypes.text.CCodePhrase
import hce.core.datastructure.itemstructure.representation.*
import archetype_repository.ArchetypeManager
import hce.core.data_types.quantity.date_time.*
import hce.core.data_types.quantity.*
import hce.core.data_types.basic.*
import hce.core.data_types.text.*
import hce.core.RMObject
import hce.core.common.archetyped.*
import hce.core.support.identification.*
import org.codehaus.groovy.grails.commons.ApplicationHolder
import com.thoughtworks.xstream.XStream




/**
 *
 * @author leacar21
 */
class BindingAOMRM {

    final String ELEMENT = "ELEMENT"
    final String CLUSTER = "CLUSTER"
    

    // Inicializo HashMap en donde almaceno los objetos bindeados del RM.
    // Mapa ArqId->Locatable, donde el locatable se coresponde con el nodo raiz del arquetipo.
    //LinkedHashMap<String, Locatable> mapaRMO = new LinkedHashMap<String, Locatable>()
    def mapaRMO = [:]
    
    
    // Estructura que mapea, nombre de arquetipo, con objeto Locatable
    // en donde se instancia el bindeo para ese arquetipo
    // Inicializo lista de referencias a ArchetypeSlot
    // LinkedHashMap<String, Locatable> mapaRefArchSlot = new LinkedHashMap<String, Locatable>() 
    
    // SOLUCION INICIAL DE SLOT
    //def mapaRefArchSlot = [:]
    
    // Todas estas tienen la misma clave
    def tempSlotRefs = [:] // slotNodePath => arquetipo referenciado
    def tempSlotRefsSlots = [:] // similar a al anterior pero para la path guardo el slot
    def tempSlotResfsAttrs = [:] // similar al anterior, es para resolver que no tengo el nombre del atributo al tener el slot (porque slot.getParent() retorna null, es un bug y ya lo reporte en la ML)
    
    // Resultado final del procesamiento de los 3 mappings anteriores.
    def slotRefs = []
    
    // Almacena el resultado del bindeo
    // PAB:
    // FIXME: no se si es necesaria, el resultado se retorna del metodo bind.
    Locatable resultBindeo
    
    
    // Lista de errores surgidos en el bindeo por chequeo de ocurrencias de los nodos.
    List<String> listaErrores = []
    
    // ====================================================================
    // Mapa ruta->lista de errores para un campo.
    // Esta es nueva para los errores de formato y otros errores que no se pongan en listaErrores.
    // FIXME: luego las listas de errores deberian ser una sola.
    def errors2 = [:]

    def getErrors2()
    {
        return this.errors2
    }
    
    static String ERROR_BAD_FORMAT = "error.badFormat"
    // ====================================================================
    
        
    // PAB: me quedo con la instancia global de FactoryObjectRM para no pedirla cada vez.
    def rmFactory = FactoryObjectRM.getInstance()
    
    // -------------------------------------------------------------------
    
    /**
     * Metodo auxiliar para obtener la path a un nodo con el id del arquetipo al
     * que pertenece (igual a como vienen las paths con sus valores de la GUI).
     */
    def fullPath( Archetype a, ArchetypeConstraint c )
    {
        return a.archetypeId.value + c.path()
    }
    
    /**
     * Retorna el ultimo bindeo realizado con el objeto
     * PAB: el objeto se retorna, creo que no es necesario este metodo.
     *
     * @return Ultimo bindeo
     */
    Locatable getResultBindeo()
    {
        return resultBindeo
    }

    /**
     * Retorna los errores surgidos en el ultimo bindeo realizado con el objeto
     *
     * @return Ultimo bindeo
     */
    List<String> getErrores()
    {
        return listaErrores
    }
    
    /**
     * Retorna el numero de valores no vacios de la lista.
     * Se considera vacio un string vacio o un null.
     * Se usa para evaluar si hay un error por la cardinalidad de las ocurrencias, 
     * comparando con la cantidad de valores no nulos que vienen desde la web, este
     * metodo da este ultimo numero.
     */
    int countValues( List list )
    {
        int count = 0
        list.each {
            if (it) count++
        }
        return count
    }
    
    /**
     * Retorna true si el nombre rmTypeName es de un DataValue.
     * Para tipos DomainType como DvCodedText o DvQuantity retorna false.
     */
    def isDataValue( String rmTypeName )
    {
        // TODO: agregar tipos faltantes
        return ['DV_BOOLEAN',
                'DV_MULTIMEDIA',
                'DV_COUNT',
                'DV_TEXT'
                ].contains(rmTypeName)
    }
    
    def isDomainType( String rmTypeName )
    {
        return ['DV_CODED_TEXT',
                'DV_QUANTITY',
                'DV_ORDINAL'
                ].contains(rmTypeName)
    }
    
    def isItemStructure( String rmTypeName )
    {
        return ['ITEM_TREE',
                'ITEM_LIST',
                'ITEM_SINGLE',
                'ITEM_TABLE',
                'ITEM_STRUCTURE' // no debe caer aca porque es abstracta...
                ].contains(rmTypeName)
    }
    
    def isItem( String rmTypeName )
    {
        return ['CLUSTER',
                'ELEMENT',
                'ITEM' // no deberia caer aca porque es abstracta
                ].contains(rmTypeName)
    }
    

    /**
     * Retorna un mapeo con nombre de arquetipo, arquetipo, para todos
     * los arquetipos incluidos en el template con id templateId
     *
     * @param templateId, nombre del template para el cual se obtendra el mapeo
     * @return mapeo nombre arquetipo, arquetipo para los arquetipos incluidos en el template
     */
    LinkedHashMap<String, Archetype> getArchetypeIdList(String templateId)
    {
       def root = "templates/hce/trauma"
       def template = "/" + templateId
       
       def filename = root + "/" + template + ".xml"
       def fileContents = ""
       new File(filename).eachLine{line-> fileContents += line}   // Contenido del archivo a String
       def templateContent = new XmlSlurper().parseText( fileContents ) // XML parseado
       
       def manager = ArchetypeManager.getInstance()
       
       // variables auxiliares para la recorrida
       def refArchetype
       def archetypeId
       def nodePath
       def archetypeList = [:] // archId => archetype
       def archetypeFields = [:] // campos por archetypeId map(archId->[path, class, CObject])
       def cobject
       
       // Nodo raiz
       archetypeId = templateContent.root_archetype.archetype.'@id'.text()
       refArchetype = manager.getArchetype( archetypeId )
       archetypeList[archetypeId] = refArchetype

       templateContent.included_archetypes.archetype.each{ archetypeNode ->
       
          archetypeId = archetypeNode.'@id'.text()
          //println "archetypeId: " + archetypeId

          refArchetype = manager.getArchetype( archetypeId )
          archetypeList[archetypeId] = refArchetype
       }
       
       return archetypeList
    }

    /**
     * Retorna el id del arquetipo raiz del template de Id templateId
     *
     * @param templateId, nombre del template para el cual se obtendra el id del arquetipo raiz
     * @return id del arquetipo raiz del template de Id templateId
     */
    String getIdArchetypeRoot(String templateId)
    {
       // FIXME: no levantar de disco que es lento, llamar al TemplateManager
       //        que ya lo tiene levantado y parseado.
       def root = "templates/hce/trauma"
       def template = "/" + templateId

       def filename = root + "/" + template + ".xml"
       def fileContents = ""
       new File(filename).eachLine{line-> fileContents += line}   // Contenido del archivo a String
       def templateContent = new XmlSlurper().parseText( fileContents ) // XML parseado

       def manager = ArchetypeManager.getInstance()

       def archetypeId = templateContent.root_archetype.archetype.'@id'.text()
       return archetypeId
    }

    //-----------------------------------------------------------------------------

    /**
     * Metodo que realiza el bind entre los datos llenados por el usuario 
     * (definidos por los arquetipos) y objetos del modelo de referencia
     *
     * El resultado del bindeo puede obtenerse con la operación getResultBindeo() y
     * los errores surgidos en el mismo con la operación getErrores()
     *
     * @param pathValor, es un mapeo entre las diferentes path de los arquetipos y
     * los valores ingresados por el usuario
     * @param templateId, es el id del template sobre el cual se realizara el bindeo
     *
     */
    def bind(LinkedHashMap<String, Object> pathsValor, String templateId)
    {
        println "== Bind"
        println "   = pathsValor: " + pathsValor
        println "   = templateId: " + templateId
        println "======================================================="
        
        LinkedHashMap<String, Archetype> arquetipos = getArchetypeIdList(templateId)
        
        // Se podria aumentar la performance, prosesando en paralelo los arquetipos. Para esto primero cargamos un mapeo <idArquetipo, lista de path>
        arquetipos.each{ entry ->
        
            String arqId = entry.key
            
            println "=== bind: arqId: " + arqId

            // Me quedo con los path que corresponden al arquetipo que se esta procesando
            def keys = pathsValor.keySet().findAll{ it.startsWith(arqId) }
            LinkedHashMap<String, Object> pathValorArquetipo = pathsValor.subMap(keys)

            // Quito el id del Arquetipo de los Path
            LinkedHashMap<String, Object> pathValorSinIdArq = [:]
            pathValorArquetipo.keySet().each{
                pathValorSinIdArq.put(it[arqId.length() .. (it.length() - 1)], pathValorArquetipo[it])
            }

            // PAB: poda previa, si no tengo pathValor para el arq, ni siquiera llamo al bindArq
            // Si hago esta poda me quedo sin los arquetipos de SECTION que tienen solo SLOTS
            // y no tienen ningun pathValor

            // PAB: (***) rmObject puede ser null si no vienen valores para bindear...
            def rmObject = bindArquetipo(entry.value, pathValorSinIdArq, templateId)

            println "||>>>||||>>>>>>>||>>>> Salida bindArquetipo: " + rmObject
            
            //completarLocatable(rmObject, entry.value.node("/").nodeID, entry.value, templateId)
            
            // (***)
            if (rmObject) // puede ser una lista!, en a1_a2 la raiz es Cluster y bindCluster tira una lista!
            {
                if (rmObject instanceof List)
                {
                    // Aunque tire una lista, la raiz siempre va a ser una, por lo que la lista tiene solo un elemento!
                    println "FIXME: tira una lista"
                    println "FIXME: tira una lista"
                    println "FIXME: tira una lista"
                    rmObject[0].archetypeNodeId = arqId
                    this.mapaRMO[arqId] = rmObject[0]
                }
                else
                {
                    // El archetypeNodeId es el archetypeId porque es nodo raiz (ref: common_im.pdf, Pagina 14)
                    rmObject.archetypeNodeId = arqId
        
                    // Guardo en un mapeo para armar el objeto RM final en base a los ArchetypeSlots
                    //this.mapaRMO.put(arqId, rmObject)
                    this.mapaRMO[arqId] = rmObject
                }
            }
        }

        // SOLUCION INICIAL DE SLOT
        /*
        println "**************************"
        println "**** COMPLETAR SLOTS *****"
        println "mapaRefArchSlot: " + this.mapaRefArchSlot // FIXME: me dice que esto es vacio...
        println "**************************"
        */
        
        // FIXME: mapaRMO podria ser global
        resultBindeo = completarRMOAS(templateId)

        // PAB
        return resultBindeo
    }

    /**
     * Metodo que une las estructuras armadas para cada archetypeSlot, presente en los
     * arquetipos que se usan para el binder, en una estructura del RM.
     * 
     * Recorro estructura global que se fue armando a medida que se bindeaba y se encontraba
     * un ArchetypeSlot, en esta estructura se mapea, id del arquetipo con un objeto del RM vacio (que pertenece
     * a un arbol padre de objetos del RM bindeados, mapaRefArchSlot). Para cada elemento de esta estructura, busco
     * el subarbol correspondiente en el mapeo mapaRMO (mapea id de arquetipo con subarbol correspondiente
     * al arquetipo). Luego copio la informacion de este ultimo objeto del RM, en el objeto del RM que se
     * encuentra referenciado en la estructura mapaRefArchSlot.
     *
     * @param templateId: Nombre del Template, de donde se obtiene el arquetipo raiz
     * @param mapaRMO: Estructura que mapea id de arquetipo con subarbol del RM bindeado
     * @return Estructura completa del RM bindeada para el template
     */
    def completarRMOAS( String templateId )
    {
        println "==== completarRMOAS: "
        println "     = mapaRMO: " + this.mapaRMO
        println "     = slotRefs: " + this.slotRefs // global
        println "     = tempSlotRefsSlots: " + this.tempSlotRefsSlots // global
        println "=========================================="
        
        // forma inicial de hacerlo
        String idArqRoot = getIdArchetypeRoot(templateId)
        def rmObjectRoot = this.mapaRMO[idArqRoot] // Quito el objeto raiz (sera el objeto que retorne)
        //this.mapaRMO.remove(idArqRoot)

        //println "|||||||||||||||||||||||||||||||||||||||||||||||||||"
        
        this.slotRefs.each { slotRef ->
        
            def owner = slotRef.owner // A quien le tengo que enchufar el objeto referenciado.
            
            //slorRef.reference // refArq
            //slotRef.ownner // objeto a setear la referencia
            def bindedRef = this.mapaRMO[slotRef.getRefArqId()] // TODO: verificar que da algo != null
            
            // TODO: ver que atributo era el de la referencia
            //       sacar el objeto vacio que esta ahora en ese atributo con el refArqId en su archetypeDetails
            //       setear o agregar el bindedRef
            // tengo que ver si el atributo es simple o multiple, para hacer setXXX o addToXXX
            
            // Obtengo el slot
            def slot = slotRef.ownerSlot // FIX: al problema de tener mal las paths en el adl parser
            
            // BUG: getParent() da null para los ArchetypeSlots,
            // es un bug y ya lo reporte en la ML, lo soluciono encajando otro map global con el attr...
            //def parentAttr = slot.getParent()
            def parentAttr = this.tempSlotResfsAttrs[slotRef.pathToSlot]
            def attr = parentAttr.rmAttributeName
            
            //println "|||||| ATTR: " + attr
            
            // Es un atributo multiple o simple?
            if ( parentAttr instanceof CMultipleAttribute )
            {
                //println "|||||| ES MULTIPLE: " + slotRef.pathToSlot
                
                // 1. Remover de la coleccion (es un attr multiple entonces es una coleccion) el elemento
                //    vacio bindeado en owner que tenga el mismo archetypeId que la referencia.
                // 2. Agregar a la coleccion la referencia bindeada con toda la informacion.
                
                
                // Todos los objetos dummies de owner.$att tienen su archetypDetails porque
                // se los setea el rmFactory.completarLocatable.
                def removeObject = owner."$attr".find{ it.archetypeDetails.archetypeId == slotRef.getRefArqId() }
                
                if (!removeObject) throw new Exception( "No encuentra al objeto vacio en el owner... con: " + slotRef.getRefArqId() )
                
                owner."$attr".remove(removeObject)
                
                // WARNING: si no vinieron valores, bindedRef puede ser null. Si meto null en
                //          la coleccion la linea de arriba de def removeObject = ow... falla 
                //          al pedir it.archDetails porque it es null :D
                if (bindedRef)
                    owner."$attr".add( bindedRef )
            }
            else // Asumo que si cae aca es CSingleAttribute, no hay otra opcion.
            {
                //println "|||||| ES SIMPLE: " + slotRef.pathToSlot
                
                // 1. Setear el atributo con el bindedRef, asi sobreescribe el objeto vacio que
                //    estaba en ese lugar.
                
                // WARNING: bindedRef puede ser null, no pasa nada porque setea null y elimina el objeto vacio que estaba antes.
                owner."$attr" = bindedRef
            }
        } // completarRMOAS
        
        //println "|||||||||||||||||||||||||||||||||||||||||||||||||||"

        // FUTURE: el resultado del bind podria no ser un unico objeto raiz,
        //         podrian ser objetos primos, al mismo nivel pero de distintos
        //         arquetipos, entonces el bind devolveria una lista de elementos.
        return rmObjectRoot
        
    } // completarRMOAS


    /**
     * Retorna el bindeo para el arquetipo dado, tomando en cuenta el mapeo pathValorArquetipo
     *
     * @param arquetipo, es el arquetipo sobre el cual se realizara el bindeo
     * @param pathValorArquetipo, mapeo de path y valores ingresados por el usuario, los cuales
     * seran bindeados a objetos del RM segun las path
     * @param tempId, id del template sobre el que se realiza el bindeo
     *
     * @return Objeto del RM correspondiente al bindeo
     */
    def bindArquetipo(Archetype arquetipo, LinkedHashMap<String, Object> pathValorArquetipo, String tempId)
    {
        println "==== bindArquetipo: " + arquetipo.archetypeId.value
        println "========================================================"
        
        CComplexObject cco = arquetipo.getDefinition()
        return bindCComplexObject(cco, pathValorArquetipo, arquetipo, tempId)
    }

    /**
     * Punto de entrada del bindeo. Llama al metodo de bindeo correspondiente al
     * tipo de objeto que sea el parametro de entrada co.
     *
     * @return objeto del RM bindeado
     */
    def bindCObject(CObject co, LinkedHashMap<String, Object> pathValorCObject, Archetype arquetipo, String tempId)
    {
        // Obtengo el nombre de la clase (nombre sin el paquete), para luego llamar al metodo correspondiente
        String nombreClase = co.getClass().getSimpleName()
        String bindMethod = 'bind' + nombreClase

        println "==== bindCObject"
        //println "   = pathValorCObject: " + pathValorCObject
        println "   = bindMethod: " + bindMethod
        //println "   = CObject class: " + co.getClass().getSimpleName()
        println "   = typeName: " + co.rmTypeName
        println "======================================================="
        
        // Llamara a bindCComplexObject, bindCPrimitiveObject, bindCDomainType, bindArchetypeSlot, bindArchetypeInternalRef
        return this."$bindMethod"(co, pathValorCObject, arquetipo, tempId)
    }

    /**
     * ownerArchetype arquetipo donde esta el nodo cco
     * cco es el nodo que se usa para bindear rmObject.
     * rmObject es el objeto ya bindeado a partir de cco, es el objeto que quiero ver si es duenio de algun slot que se haya encontrado.
     */
    def setSlotRefs(Archetype ownerArchetype, CComplexObject cco, Locatable rmObject)
    {
        println "<<<<>>>> setSlotRefs"
        
        // Quiero ver que hijos slot encuentro y saber si este nodo es el padre.
        // Me fijo si se bindeo algun slot para este nodo.
        // Supongo que el unico que puede tener hijos slots es un CComplexObject, si no es asi deberia hacer esto en otros metodos.
        def deleteSlots = [] // Slots a remover porque ya los proceso
        this.tempSlotRefs.keySet().each { refPath ->
        
            // Si la referencia parte del nodo cco, cco es su duenio
            if ( refPath.startsWith( cco.path() ) )
            {
                println "<<<<<<<<< ENCUENTRO SLOT >>>>>>>>>>>>>"
                println "<<<<<<<<< refPath: " + refPath
                println "<<<<<<<<< ownerPath: " + cco.path()
                println "<<<<<<<<< reference: " + this.tempSlotRefs[refPath].archetypeId.value
                println ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
                deleteSlots << refPath // Marco para elminar path procesada
                this.slotRefs << new SlotRef(
                               reference: this.tempSlotRefs[refPath], // arquetipo que referencia
                               owner: rmObject,
                               pathToSlot: refPath,
                               ownerArchetype: ownerArchetype,
                               ownerSlot: this.tempSlotRefsSlots[refPath]
                            )
                //println ">>>>>>>> archetype.pathNodeMap: " + ownerArchetype.pathNodeMap
            }
        }
        
        // Elimina referencias usadas
        deleteSlots.each { path ->
            this.tempSlotRefs.remove(path)
            this.tempSlotRefsSlots.remove(path)
            //this.tempSlotResfsAttrs.remove(path) // no se debe eliminar el objeto porque no se pone en SlotRef, se deja global hasta que se usa...
        }
        
    } // setSlotRefs
    
    /**
     * PAB: me parece que a veces devuelve un unico elemento y a veces una lista,
     *      es mejor tener consistencia en el tipo, por ejemplo devolver siempre
     *      una lista.
     *
     * Bindeo para objetos (nodo del arquetipo) instancias de CComplexObject. Crea el objeto del RM
     * correspondiente (segun lo indicado por la propiedad rmTypeName del objeto) tomando en cuenta
     * los datos del mapeo pathValor.
     *
     * Si el CComplexObject tiene atributos, el metodo bindea todos sus atributos y crea el
     * objeto del RM tomando en cuenta todos los Objetos del RM correspondientes a los atributos
     *
     * @return objeto del RM bindeado
     */
    def bindCComplexObject(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "==== bindCComplexObject"
        //println "   = pathValor: " + pathValor
        //println "   = attributes: " + cco.getAttributes()
        println "   = typeName: " + cco.rmTypeName
        println "======================================================="
        
        def rmTypeName = cco.rmTypeName
        
        // PAB:
        // FIXME: Ojo a veces es un objeto y a veces una lista, deberia se siempre una lista.
        def rmObject = null
        int min = cco.occurrences.getLower()
        
        // Bindeado de tipos que no quiero que bindee automaticamente sus atributos,
        // esto corta la recursion y bindea los tipos simples directamente.
        // Para: DV_BOOLEAN, DV_MULTIMEDIA, DV_COUNT, DV_TEXT.
        // No para ORDINAL, QUANTITY, CODED_TEXT que son DomainType.
        if ( this.isDataValue( rmTypeName ) )
        {
            def method = "bind" + rmTypeName
            rmObject = this."$method"(cco, pathValor, arquetipo, tempId) // es una lista de DataValue, podria ser vacia!
            return rmObject // es una lista SIEMPRE!
        }

        // Para CLUSTER se tienen que resolver casos especiales como cuando es multiple
        // la ocurrencia del CLUSTER, se deben manejar los valores multiples distinto a
        // como se manmejan para ELEMENT.
        if ( rmTypeName == "CLUSTER" )
        {
            rmObject = this.bindCLUSTER(cco, pathValor, arquetipo, tempId)
            return rmObject // es una lista siempre
        }
        

        String factoryRMMethod = 'create' + cco.rmTypeName

        // PAB:
        // Me parece que aca hay un error, pregunta por si attributes no es null para saber
        // si son atributos simples o complejos?
        // Siempre que tenga atributos, tanto simples como complejos, la lista de atributos
        // no sera null, si esto esta mal, la solucion es preguntar por el tipo de los atributos
        // (CSingleAttribute o CMultipleAttribute) 
        if (cco.getAttributes()) // con atribtos
        {
            println "-------CA>>>> " + cco.rmTypeName
            
            // Lista de lista de objetos bindeados para cada atributo del CCObject
            List<Object> listaListRMO = []

            cco.getAttributes().each { cattr ->
            
                // Me quedo con los path que corresponde al atributo que se esta procesando
                LinkedHashMap<String, Object> pathValorAtribute = pathValor.findAll{it.key.startsWith(cattr.path())}
                def listRMO
                
                // TODO:
                // Si no tengo valores para ese atributo ya se que va a retornar null,
                // se podria verificar si hay valores antes de hacer la llamada y si no
                // hay, retornar null directamente. Habria que hacer la verificacion de
                // errores por ocurrencia igual.
                
                // Abajo aun deberia verificar, si es que mande a bindear, y si esperaba un
                // minimo de objetos para el nodo del arquetipo, y obtengo menos objetos,
                // debe dar un error de que hay menos objetos de los esperados.
                
                // TEST: puse lo de SECTION para probar los slots, las SECTIONs no tienen pathsValor
                //       asi que no sale nada del bind y esta poda mata que siga bindeando los slots
                //       de la SECTION... si es SECTION quiero que entre...

                // PAB: si es un atributo multiple bindea segun los miembros,
                //      por ejemplo, si es 'items' de un ITEM_TREE, aca devuelve
                //      los CLUSTERs y ELEMENTs que van adentro de 'items'.
                
                listRMO = bindAttribute(cattr, pathValorAtribute, arquetipo, tempId)
                listaListRMO.add(listRMO)
            }
            
            // FIXME:
            // Aca deberia venir el proceso de los rmObjects bindeados para cada atributo.
            // Si es multiple, para cada atributo voy a tener varios rmObjs.
            // Para crear ELEMENTS, le deberia mandar de a uno los rmObjs, en este caso el
            // atributo es uno solo: value.
            if (cco.rmTypeName == "ELEMENT" )
            {
                // FIXME: verificar errores por ocurrencias
                if (listaListRMO.size()==0) // No pudo bindear el attr 'value'
                {
                    // TODO: verificar si hay un caso que caiga aca, me parece que siempre
                    //       que siempre mete una lista en la lista, aunque puede ser vacia.
                    /* FIXME:
                     * El error de ocurrencia se debe verificar en el ELEMENT, pero deben ponerse
                     * las paths dependiendo del tipo de su value. Por ejemplo si es unQuantity,
                     * hay que ver si se ponen errores para las 2 paths, y el element debe saber
                     * los nombres de los atributos sabiendo el tipo del value.
                     * Esto se resolveria llamando a un metodo que se encargue de averiguar
                     * los nombres de lo atributos, asi aqui veo so el element tiene
                     * las ocurrencias correctas y se que  es porque faltan valores para
                     * paths de sus atributos
                     */
                    if (cco.occurrences.getLower()>0) // Se necesitaba el value
                    {
                        listaErrores.add( fullPath(arquetipo,cco) )
                    }
                    
                    return null // null element
                }
                if (listaListRMO.size()==1) // Ha bindeado el 'value'
                {
                    def rmObjectsForValue = listaListRMO[0] // Puede ser una lista de objetos
                    
                    /* FIXME:
                     * El error de ocurrencia se debe verificar en el ELEMENT, pero deben ponerse
                     * las paths dependiendo del tipo de su value. Por ejemplo si es unQuantity,
                     * hay que ver si se ponen errores para las 2 paths, y el element debe saber
                     * los nombres de los atributos sabiendo el tipo del value.
                     * Esto se resolveria llamando a un metodo que se encargue de averiguar
                     * los nombres de lo atributos, asi aqui veo so el element tiene
                     * las ocurrencias correctas y se que  es porque faltan valores para
                     * paths de sus atributos
                     */
                    if ( countValues(rmObjectsForValue) < cco.occurrences.getLower() )
                    {
                        listaErrores.add( fullPath(arquetipo,cco) )
                        return null // null element
                    }
                    else
                    {
                        def elements = [] // Lista para retornar
                        rmObjectsForValue.each { bindedElementValue ->
                        
                            if (bindedElementValue)
                            {
                                elements << rmFactory."$factoryRMMethod"(bindedElementValue, arquetipo, arquetipo.node(cco.path()).nodeID, tempId)
                            }
                        }
                        return elements // lista de elementos
                    }
                }
                // NO PUEDE HABER OTRO CASO, POR LAS DUDAS PODRIA TIRAR EXCEPT....
            } // caso ELEMENT
            else
            {
                // FIXME: todo este codigo lo dejo para otros casos, no se si para CLUSTERS hay que hacer algo
                //        parecido que para ELEMENT, DV_COUNT o DV_BOOLEAN que se hace arriba.
                
                // PAB:
                // TODO: para otros tipos como CLUSTER o ITEM_SINGLE creo que se deberia hacer el mismo chequeo...
                // Si es ELEMENT, si listaListRMO == [[]] quiere decir que el value del ELEMENT es null
                
                
                // FIXME: verificacion de ocurrencias.
                // Para probar el binder con SLOTS no quiero que chequee nulos para SECTIONS
                // Para los ItemStruture no quiero chequeo de ocurrencias, es solo para nodos CLSUTER Y ELEMENT.
                // Si es item tree o itemlist podria verificar la cardinalidad de los Items, pero si habia restricciones sobre los items,
                // esos errores ya se van a mostrar.
                //if (cco.rmTypeName != "SECTION" && !isItemStructure(cco.rmTypeName ))
                //if (cco.rmTypeName == "CLUSTER") // quiero verificar solo para cluster y element, element se verifico en el caso anterior.
                /* PAB: 27-01-2010: me parece que con la verificacion que se hace del element es suficiente para mostrar errores en la web.
                if (cco.rmTypeName != "SECTION")
                {
                    // FIXME: la verificacion deberia hacerse luego de bindera los CLUSTERs
                    //        en listaListRMO creo que vienen los atributos para meter adentro de UN solo cluster.
                    // Por otro lado, el CLUSTER TIENE SU PROCESAMIENTO ANTES DE QUE SE EJECUTE ESTE CODIGO!!!
                    
                    // Aplano la lista y si es vacia
                    // PAB: y en este caso no deberia llamar a $factoryRMMethod (***)
                    if ( listaListRMO.flatten() == [] )
                    {
                        if (min > 0)
                        {
                            // PAB:
                            // el elemento era requerido y no viene ningun valor, se pone un error en la lista de errores.
                            // TODO: la condicion deberia ser si vienen menos valores del minimo (list.flat.size() < min)
                            //       no si min > 0 y list.flat.size() == 0 que es lo que se esta chueueando.
                            println " ----> ERROR 666 : obligatorio el valor para la path: " + fullPath(arquetipo,cco) + " ("+ cco.rmTypeName + ")"
                            listaErrores.add( fullPath(arquetipo,cco) )
                        }

                        return rmObject // PAB: es null porque se que no tengo valores para meterle
                    }
                }
                */
                
    
                println "~~~~~~~~~~~~~~~~~~~~~~~~~"
                println "-- -- -: Factory Method: " + factoryRMMethod
                println listaListRMO.flatten()
                println "~~~~~~~~~~~~~~~~~~~~~~~~~"
                
                // Creo Objeto del modelo de referencia correspondiente (con los objetos del RM
                // devueltos por el bindAttribute en la iteracion por su lista de atributos)
                // PAB: ojo! $factoryRMMethod aca recibe como 1er parametro una lista de objetos RM,
                //      y en la llamada de la rama ELSE de este IF recibe el mapa de path/valor,
                //      puede llevar a problemas usar un parametro comodin.
                //      Se solucionaria si aca se recorre la lista y se llama con cada objeto.
                // FIXME: primer parametro...
                rmObject = rmFactory."$factoryRMMethod"(listaListRMO, arquetipo, arquetipo.node(cco.path()).nodeID, tempId)
                
                // FIXME: a veces tira LinkedList otras Cluster, Action, Evaluarion, ItemTree, etc
                //        que siempre tire lista aunque sea de un elemento, o ninguno.
                // Si es un primitice, podria tirar multiples, pero si es de item_structure para arriba,
                // va a tirar uno solo. Talvez lso procesamientos de unos y otros deban hacerse en metodos
                // distintos (para no tener que hacer IF por cada caso).

                
                // FIXME: luego se deberia corregir, que todo lo de rmFactory tire 1 solo objeto,
                //        no listas, esas listas las arma el binder.
                // Verifico slots hijos de este nodo
                // setSlotRefs(CComplexObject cco, Locatable rmObject)
                if (rmObject instanceof LinkedList)
                {
                    rmObject.each { obj ->
                    
                        if (obj instanceof Locatable) // Puede salir un DvValue
                            setSlotRefs( arquetipo, cco, obj )
                    }
                }
                else
                    if (rmObject instanceof Locatable) // Puede salir un DvValue
                        setSlotRefs( arquetipo, cco, rmObject )
            }
        }
        else // Sin Atributos
        {
            // Cae aca cuando es DV_TEXT, DV_COUNT, ...

            println "-------SA>>>> " + cco.rmTypeName

            // FIXME: ver como determina el nodeId en el otro caso, creo que es distinto...
            String archNodeId = ""
            if (arquetipo.node(cco.path()).nodeID)
            {
                archNodeId = arquetipo.node(cco.path()).nodeID
            }

            // Creo Objeto del modelo de referencia correspondiente (con los path que vienen como parametro)
            // PAB: si cae aca, pathValor no es una lista de RMs, y eso es lo que espera por
            //      lo menos de ELEMENT para arriba...
            //rmObject = rmFactory."$factoryRMMethod"(pathValor, arquetipo, archNodeId, tempId)
            
            // PARA UN CDvQuantity creo que vendrian 2 paths...
            if (pathValor.size() == 1)
            {
                def result = []
                
                // FIXME: con que termine depende del tipo que bindeo, es el nombre del atributo, no siempre es 'value'.
                //def values = pathValor.find{it.key.endsWith("value")}?.value
                def values = pathValor.find{ true }.value // Obtiene los valores de la unica entry del map
                if (values.getClass().isArray()) // Valores multiples
                {
                    def valuesList = values as List
                    
                    // FIXME: los valores pueden estar pero ser strings vacios... es lo mismo que si no estan!!!!
                    // FIXED: countValues cuenta los valores no nulos.
                    // (AAA) verificacion de errores de ocurrencias para cuando hay multiples valores
                    if ( countValues(valuesList)<min) // hay menos elementos de los que se esperaban.
                    {
                        listaErrores.add( fullPath(arquetipo,cco) )
                    }
                    
                    valuesList.each { value ->
                    
                        println " //////////////////////////////// VALUE 1: '" + value +"'" 
                    
                        if (value)
                        {
                            // WARNING:
                            // 'value' es un string, se deberia hacer la conversion al tipo correcto dependiendo del
                            // rmTypName, luego esta logica deberia estar en un metodo aparte.
                            if (cco.rmTypeName == "DV_COUNT" )
                            {
                                try // chequeo de formato
                                {
                                    value = Integer.parseInt(value) // TODO: podria tirar except, agarrar y poner erro en errors2
                                }
                                catch(Exception e)
                                {
                                    def path = pathValor.keySet().toArray()[0]
                                    def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                                    if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                                    errors2[fullPath] << ERROR_BAD_FORMAT
                                    //return null
                                }
                            }

                            // FIXME: si da except arriba no deberia invocar este metodo!
                            result << rmFactory."$factoryRMMethod"(value, arquetipo, archNodeId, tempId)
                        }
                    }
                }
                else // Valor simple
                {
                    println " //////////////////////////////// VALUE 2: '" + values + "'" 
                    
                    if (!values) // Si es vacio o null
                    {
                        if (min > 0) listaErrores.add( fullPath(arquetipo,cco) )
                    }
                    else
                    {
                        // FIXME: si da except arriba no deberia invocar este metodo!
                        result << rmFactory."$factoryRMMethod"(values, arquetipo, archNodeId, tempId)
                    }
                }
                
                //println " ======== RESULT: " + result
                
                return result // lista de objetos simples rm
            }
            else
            {
                // TODO: verificar cuando cae aca, si es que cae (deberia ser una fecha o un quantity
                //       sin definicion de restricciones para sus atributos).
                println "== Path Valor tiene mas de una key: " + pathValor
                println "==============================================================================="
            }
        }

        return rmObject
        
    } // bindCComplexObject

    
    /**
     * Bindeo para objetos (atributos de CComplexObject) instancias de Attribute. Crea objetos del RM
     * correspondiente a cada children del atributo tomando en cuenta el mapeo pathValorAttribute.
     *
     * @return objetos del RM bindeados
     */
    def bindAttribute(CAttribute cattr, LinkedHashMap<String, Object> pathValorAttribute, Archetype arquetipo, String tempId)
    {
        println "==== bindAttribute"
        println "   = name: " + cattr.rmAttributeName
        println "=========================================="
        // PAB: el atributo depende del rm_type_name del CObject que se esta bindeando.
        
        // cattr.rmAttributeName sera "items" , "value" o "magnitude", etc (sera
        // un multiple attribute o un single attribute)

        // Creo la lista de items que voy a retornar
        List<Object> listaRMObject = []

        // Recorro los CObjects del CAttribute
        cattr.getChildren().each{ co ->
        
            // Me quedo con los path que corresponde al children (CObject) que se esta procesando
            LinkedHashMap<String, Object> pathValorCObject = pathValorAttribute.findAll{it.key.startsWith(co.path())}
            
            // PAB: poda previa, si no tengo valores para bindear no llamo a bindCObject
            // TEST: si es una referencia con SLOT no va a tener pathValor... esta poda me corta el bind de slots...
            //if (pathValorCObject.size() > 0)
            //{
                def rmObject = bindCObject(co, pathValorCObject, arquetipo, tempId)
    
                println "========================================"
                println "==== Objetos Bindeados en Attribute ===="
                println rmObject
                println "========================================"
                
                //------------------------------------
                // Agregado para multiples ocurrencias
                //------------------------------------
                
                // PAB:
                // FIXME: mas facil si siempre devuelve una lista, cuando es un solo elemento es una lista con ese elemento.
                if (rmObject)
                {
                    if (rmObject instanceof List)
                    {
                        rmObject.each{ rmo ->
                        
                            if (rmo)
                                listaRMObject.add(rmo)
                        }
                    }
                    else
                    {
                        listaRMObject.add(rmObject)
                    }
                }
            //}
        }
                
        // Necesario para el bind de slots
        // Soluciona el problema de que no puedo obtener el parent
        // attribute de los archetypeSlots porque getParent retorna
        // null, y es necesario para saber el nombre del atributo que
        // tiene el o los slots.
        // El proceso es similar al metodo setSlotRefs()
        //  - ver si alguna referencia a un slot fue encontrada en algun hijo
        //  - si se encuentra, guardar la referenca a este atributo en una estructura global
        
        this.tempSlotRefs.keySet().each { refPath ->
        
            // Si la referencia parte del nodo cattr, cattr es su duenio
            if ( refPath.startsWith( cattr.path() ) )
            {
                println "<<<<<<<<< ENCUENTRO SLOT >>>>>>>>>>>>>"
                println "<<<<<<<<< refPath: " + refPath
                println "<<<<<<<<< ownerPath: " + cattr.path()
                println "<<<<<<<<< reference: " + this.tempSlotRefs[refPath].archetypeId.value
                println ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
                
                this.tempSlotResfsAttrs[refPath] = cattr
            }
        }
        
        // Retorno la lista con los objetos del RM creado, que podra contener
        // uno elemento en caso de ser cattr.rmAttributeName = "value" (por ejemplo)
        // y mas de un elemento en caso de que cattr.rmAttributeName = "item"
        return listaRMObject
        
    } // bindAttribute


    /**
     * Bindeo para objetos instancias de CPrimitiveObject. Crea un objeto primitivo del RM.
     *
     * @return objeto del RM bindeado
     * PAB: este metodo no hace nada de valor solo llama al otro, llamar al otro directamente o poner el codigo del otro metodo aca.
     */
    def bindCPrimitiveObject(CPrimitiveObject cpo, LinkedHashMap<String, Object> pathValor, arquetipo, tempId)
    {
        println "==== bindCPrimitiveObject"
        println "   = pathValor: " + pathValor
        println "   = rmType: " + cpo.rmTypeName
        println "=========================================="
        
        // PAB:
        // Mismo chequeo que en bindCComplexObject por el min de las occurrences del nodo
        // del arquetipo viendo si el nodo es obligatorio y no viene ningun valor.
        
        // FIXME:
        // El min creo que no se deberia verificar para CPrimitiveObject, si no para el
        // CComplexObject ELEMENT duenio de este primitivo, y es ahi donde se deberian
        // crear las multiples ocurrencias tambien.
        //
        // TODO: Min aqui creo que siempre da 0, verificar.
        int min = cpo.occurrences.getLower()
               
        // PAB:
        // Si no tengo valores retorno null, y verifico si el nodo es obligatorio.
        if (pathValor.size()==0)
        {
            if (min>0) listaErrores.add( fullPath(arquetipo,cpo) )
            return null
        }
        
        // En lugar de ir a CPrimitive llamo directo al metodo que crea instancias
        //return bindCPrimitive(cpo.item, pathValor, arquetipo, tempId) // TODO item puede ser null
        
        // FIXME: PAB???
        String factoryRMMethod = 'create' + cpo.rmTypeName
        
        if (pathValor.size() == 1)
        {
            def result = []
            
            // FIXME: con que termine depende del tipo que bindeo, es el nombre del atributo, no siempre es 'value'
            // Lo correcto es pedir por la path de este nodo...
            //def values = pathValor.find{it.key.endsWith("value")}?.value
            
            // Pido la unica entrada del mapa
            def values = pathValor.find{ true }.value
            if (values.getClass().isArray()) // Valores multiples
            {
                def valuesList = values as List
                
                // FIXME: corregir chequeo de errores de ocurrencia para multiples valores.
                // FIXME: los valores pueden estar pero ser strings vacios... es lo mismo que si no estan!!!!
                // FIXED: countValues cuenta los valores no nulos.
                // (AAA) verificacion de errores de ocurrencias para cuando hay multiples valores
                if ( countValues(valuesList)<min) // hay menos elementos de los que se esperaban.
                {
                    listaErrores.add( fullPath(arquetipo,cpo) )
                }
                
                valuesList.each { value ->
                
                    println " //////////////////////////////// VALUE 1: '" + value +"'" 
                    
                    // Corregido como se verifican errores de ocurrencias para multiples valores.
                    if (value)
                       result << rmFactory."$factoryRMMethod"(value, arquetipo, cpo.nodeID, tempId)
                }
            }
            else // Valor simple
            {
                println " //////////////////////////////// VALUE 2: '" + values + "'" 
                
                if (!values) // Si es vacio o null
                {
                    if (min > 0) listaErrores.add( fullPath(arquetipo,cpo) )
                }
                else
                {
                    try
                    {
                        // TODO: verificar que tipo de excepcion tira para saber que poner en errors2
                        result << rmFactory."$factoryRMMethod"(values, arquetipo, cpo.nodeID, tempId)
                    }
                    catch(Exception e)
                    {
                        def path = pathValor.keySet().toArray()[0]
                        def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                        if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                        errors2[fullPath] << ERROR_BAD_FORMAT
                        return null
                    }
                }
            }
            
            println " ======== RESULT: " + result
            
            return result // lista de objetos simples rm
        }
        
        // FIXME: Si es quantity no vendria a CPrimitiveObject, seria un DomainType.
        // Si pathValor tiene 2 o mas entradas (caso posible en dates y quantity)
        //throw new Exception("pathValor tiene 2 o mas entradas para este nodo")
        
        // Viejo bindCPrimitive
        String bindMethod = 'bind' + cpo.item.getClass().getSimpleName()
        
        println "==== bindCPrimitive"
        println "  == Llama a: " + factoryRMMethod
        println "  == Type: " + cpo.item.getType()
        println "===================================="
        

        return this."$bindMethod"(cpo.item, pathValor, arquetipo, tempId)
        
    } // bindCPrimitiveObject


    /**
     * PAB:
     * Nunca se va a invocara este metodo porque CDomainType es abstracta,
     * entonces en bindCObject ningun nodo de entrada es CDomainType, pero
     * si puede ser una de sus subclases concretas.
     * 
     * Bindeo para objetos instancias de CDomainType. Crea un objeto del RM.
     *
     * Llama a la operacion de bindeo correspondiente a la clase concreto de cdt.
     *
     * @return objeto del RM bindeado
     */
    def bindCDomainType(CDomainType cdt, LinkedHashMap<String, Object> pathValorCDomainType, Archetype arquetipo, String tempId)
    {
        String nombreClase = cdt.getClass().getSimpleName()
        String bindMethod = 'bind' + nombreClase
        println "nombreClase CDomainType: " + nombreClase
        def rmObject = this."$bindMethod"(cdt, pathValorCDomainType, arquetipo, tempId)
        return rmObject
    }

    /**
     * Bindeo para objetos instancias de ArchetypeSlot. Agrega un elemento
     * al mapeo mapaRefArchSlot que mapea id de arquetipo con objeto del RM
     * en donde deberia bindearse el arquetipo.
     *
     * Luego de finalizado el bindeo del template, la operacion completarRMOAS
     * se encarga de armar el arbol del RM completo para el template tomando en cuenta
     * el mapeo mapaRefArchSlot.
     */
    def bindArchetypeSlot(ArchetypeSlot cdt, LinkedHashMap<String, Object> pathValorArchetypeSlot, Archetype arquetipo, String tempId)
    {
        println "==== bindArchetypeSlot"
        
        //String archetypeRefId
        String type
        String idMatchingKey
        
        Set<Assertion> conjAssertion = cdt.includes
        conjAssertion.each{eachAssertion -> 
        
            def item = eachAssertion.expression.rightOperand.item
            // Construyo id de arquetipo
            if (item.type == "String")
            {
                idMatchingKey = ((CString)(item)).pattern
                type = (((CString)(item)).pattern - "openEHR-EHR-").split()[0]
                // Me quedo con el tipo del arquetipo. (openEHR-EHR-OBSERVATION.prueba6.v1 -> OBSERVATION)
                // siendo ((CString)(item)).pattern igual a openEHR-EHR-OBSERVATION.prueba6.v1
                // Le quito el comienzo: "openEHR-EHR-"
                // Luego me queda OBSERVATION.prueba6.v1
                // A esto le hago un split por el "." y me quedo con el primer string: OBSERVATION
            }
        }

        // FIXME: Que pasa si no encuentra type y matchingKey?
        
        println "==== Slot Type: " + type
        println "==== IdMatchingKey: " + idMatchingKey
        println "==============================================="
        println ""
        
        // Otra opcion para lo anterior
        //    String expReg = asser.expression.rightOperand.item.pattern
        //    def manager = ArchetypeManager.getInstance()
        //    def refArchetype = manager.getArchetype(expReg) // FIXME

        // Obtengo el arquetipo referenciado
        def manager = ArchetypeManager.getInstance()
        
        // FIXME: podria obtener varios arquetipos.
        def arqRef = manager.getArchetype(type, idMatchingKey) // Obtengo el arquetipo que machea con idMatchingKey siendo de tipo type

        // FIXME: que pasa si no encuentra arqRef?
        
        // Obtengo el rmTypeName de la raiz del arquetipo
        CComplexObject cco = arqRef.getDefinition()
        String tipoRM = cco.rmTypeName

        println "==== tipoRM: " + tipoRM
        
        // Creo un objeto del RM del tipo indicado por rmTypeName
        // FIXME: para que se crea un objeto vacio? no alcanza con guardar la path? o path y tipo?
        //Locatable rmObject = rmFactory.createLOCATABLE(tipoRM)
        //def rmObject = rmFactory.createLOCATABLE(tipoRM)
        def rmObject = rmFactory.createLOCATABLE(tipoRM, cco.nodeID, arqRef, tempId) // Ahora le paso los datos para que pueda completar el locatable con los archetypeDetails, los datos son del arquetipo referenciado, no del que estoy recorriendo.

        println "==== guardo slot a: " + rmObject
        println "==== desde el slot: " + cdt.path()
        
        //this.mapaRefArchSlot.put(arqRef.archetypeId.value, rmObject)

// SOLUCION INICIAL DE SLOT
//        this.mapaRefArchSlot[arqRef.archetypeId.value] = rmObject
        
        // temporal para que el nodo padre CComplexObject reclame el slot como suyo
        // FIXME: si es un atributo multiple, una unica path seria la misma para varios elementos,
        //        por lo que el segundo elemento que tenga misma cdt.path sobreescribe el primero.
        //
        // BUG: Parece ser un bug del adl parser que para el SECTION.items tieme path /items,
        //      y para cada children de ese atributo tiene la misma path /items, en lugar de
        //      /items[slotNodeID]. Esto pasa en el caso de la SECTION con slots, hay que probar
        //      otros casos.
        //      Hago un fix rapido agregando el slotNodeId a la path como clave del map tempSlotRefs.
        //
        //this.tempSlotRefs[ cdt.path() ] = arqRef // Como deberia ser si el slot tiviera bien la path.
        this.tempSlotRefs[ cdt.path() + "["+ cdt.nodeID +"]" ] = arqRef // FIX de Pablo.
        // Tambien necesito el slot para pedirle el parent attr, asi que hago otra estructura para poder verlo.
        this.tempSlotRefsSlots[ cdt.path() + "["+ cdt.nodeID +"]" ] = cdt // FIX de Pablo.
        
        // WARNING! (***)
        // ESTA ES LA CLAVE DEL ENGANCHE DE LAS ESTRUCTURAS REFERENCIADAS CON SLOTS.
        // Al retornar el objeto vacio, se lo setea a la section que tenia
        // el slot, y como mapaRefArchSlot tiene un puntero a este, luego 
        // cuando se clona el objeto referenciado en este objeto en el 
        // mapaRefArchSlot desde completarRMOAS, la referencia desde el 
        // slot se actualiza y el objeto sale completo.
        return rmObject
        
    } // bindArchetypeSlot

    
    /**
     * Bindeo para objetos instancias de ArchetypeInternalRef. 
     * TODO: explicar que hace y como.
     */
    def bindArchetypeInternalRef(ArchetypeInternalRef air, LinkedHashMap<String, Object> pathValorInternalRef, Archetype arquetipo, String tempId)
    {
        LinkedHashMap<String, Object> pathValorIR = pathValorInternalRef.findAll{it.key.startsWith(air.path())}

        // Quito de las path la parte correspondiente a la path al internal ref
        LinkedHashMap<String, Object> pathValor = new LinkedHashMap<String, Object>()
        pathValorIR.each{it ->
            // Si la path es una path que referencia al target path la incluyo
            if (it.key.contains(air.targetPath)){
                pathValor.put(it.key.replace(air.path(), ""), it.value)
            }
        }

        def co = arquetipo.node(air.targetPath)
        def rmObject = bindCObject(co, pathValor, arquetipo, tempId)

        println "---bindArchetypeInternalRef: rmObject: " + rmObject
        
        // PAB:
        // Si bindCObject devuelve una lista, completar locatable espera un objeto locatable
        // asi que va a tirar una except...
        // completarLocatable(Locatable locatable, String archNodeId, Archetype archetype, String tempId)
//        FactoryObjectRM.getInstance().completarLocatable(rmObject, arquetipo.node(air.targetPath).nodeID , arquetipo, tempId)

        return rmObject
        
    } // bindArchetypeInternalRef

    
    //-----------------------------------------------------------------------------
    // CPrimitive

    def bindCBoolean(CBoolean cb, LinkedHashMap<String, Object> pathValorCBoolean, Archetype arquetipo, String tempId){

        if (pathValorCBoolean.size() == 0){
            return null // No hay valores para este 
        }
        if (pathValorCBoolean.size() == 1){
            //return new DvBoolean(value: Boolean.parseBoolean(pathValorCBoolean.find{true}.value))
            return new Boolean(Boolean.parseBoolean(pathValorCBoolean.find{true}.value))
        }
        
        throw new Exception("bindCBoolean: Colección de pathValor tiene mas de un elemento.")
    }

    def bindCDate(CDate cd, LinkedHashMap<String, Object> pathValorCDate, Archetype arquetipo, String tempId)
    {
        // Encuentro los valores en la coleccion de path valor
        String year = pathValorCDate.find{it.key.endsWith("year")}?.value
        String month = pathValorCDate.find{it.key.endsWith("month")}?.value
        String day = pathValorCDate.find{it.key.endsWith("day")}?.value

        if ((year != null) && (month != null) && (day != null))
        {
            // Creo un string con formato ISO 8601
            String fechaISO8601 = FactoryObjectRM.crearFechaISO8601(year, month, day, "", "", "")
            //return new DvDate(value: fechaISO8601)
            return fechaISO8601
        }
        else
        {
            return null
            //throw new Exception("bindCDate: Colección de pathValor no tiene path a 'year' o 'month' o 'day'.")
        }
    }

    def bindCDateTime(CDateTime cdt, LinkedHashMap<String, Object> pathValorCDateTime, Archetype arquetipo, String tempId){
        // Encuentro los valores en la coleccion de path valor
        String year = pathValorCDateTime.find{it.key.endsWith("year")}?.value
        String month = pathValorCDateTime.find{it.key.endsWith("month")}?.value
        String day = pathValorCDateTime.find{it.key.endsWith("day")}?.value
        String hour = pathValorCDateTime.find{it.key.endsWith("hour")}?.value
        String minute = pathValorCDateTime.find{it.key.endsWith("minute")}?.value
        String seg = pathValorCDateTime.find{it.key.endsWith("seg")}?.value

        if ((year != null) && (month != null) && (day != null) && (hour != null)){
            // Creo un string con formato ISO 8601
            String fechaISO8601 = FactoryObjectRM.crearFechaISO8601(year, month, day, hour, minute, seg)
            return fechaISO8601
        }
        else{
            return null
            //throw new Exception("bindCDateTime: Colección de pathValor no tiene path a 'year' o 'month' o 'day' u 'hour'.")
        }

    }

    def bindCDuration(CDuration cd, LinkedHashMap<String, Object> pathValorCDuration, Archetype arquetipo, String tempId)
    {
        // Encuentro los valores correspondientes al dateTime inicial en la coleccion de path valor
        String yearIni = pathValorCDuration.find{it.key.endsWith("initialValue_year")}?.value
        String monthIni = pathValorCDuration.find{it.key.endsWith("initialValue_month")}?.value
        String dayIni = pathValorCDuration.find{it.key.endsWith("initialValue_day")}?.value
        String hourIni = pathValorCDuration.find{it.key.endsWith("initialValue_hour")}?.value
        String minuteIni = pathValorCDuration.find{it.key.endsWith("initialValue_minute")}?.value
        String segIni = pathValorCDuration.find{it.key.endsWith("initialValue_seg")}?.value

        // Encuentro los valores correspondientes al dateTime final en la coleccion de path valor
        String yearFin = pathValorCDuration.find{it.key.endsWith("finalValue_year")}?.value
        String monthFin = pathValorCDuration.find{it.key.endsWith("finalValue_month")}?.value
        String dayFin = pathValorCDuration.find{it.key.endsWith("finalValue_day")}?.value
        String hourFin = pathValorCDuration.find{it.key.endsWith("finalValue_hour")}?.value
        String minuteFin = pathValorCDuration.find{it.key.endsWith("finalValue_minute")}?.value
        String segFin = pathValorCDuration.find{it.key.endsWith("finalValue_seg")}?.value

        if ((yearIni != null) && (yearFin != null)){
            // Creo un string con formato ISO 8601
            String fechaISO8601Ini = FactoryObjectRM.crearFechaISO8601(yearIni, monthIni, dayIni, hourIni, minuteIni, segIni)
            String fechaISO8601Fin = FactoryObjectRM.crearFechaISO8601(yearFin, monthFin, dayFin, hourFin, minuteFin, segFin)
            return fechaISO8601Ini + "/" + fechaISO8601Fin
        }
        else{
            return null
            //throw new Exception("bindCDuration: Colección de pathValor no tiene path a 'inicialValue_year' o 'finalValue_month' o 'day' u 'hour'.")
        }
        
        //TODO
    }

    //-----------------------------------------------------------------------------
    // PAB: este es mas del factoryRM que del binder.
    def bindCInteger(CInteger ci, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "==== bindCInteger"
        println "  == pathValor: "+ pathValor
        println "===================================="
        
        // No hay valores para este nodo 
        if (pathValor.size() == 0) return null 
        
        // FIXME: Puede haber una path pero multiples valores...
        if (pathValor.size() == 1)
        {
            // Si viene un valor pero no es un entero valido
            try
            {
                return new Integer(Integer.parseInt(pathValor.find{true}.value)) // find(true) retorna el primer elemento de la coleccion (en este caso deberia haber un solo elemento)
            }
            catch (Exception e)
            {
                // ERROR: no se pudo parsear el valor
                // TODO: podria preguntar por el tipo de la except... igual se que la except es por el mal parseo... java.lang.NumberFormatException
                
                // TODO: metodo para hacer esto
                def path = pathValor.keySet().toArray()[0]
                def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                errors2[fullPath] << ERROR_BAD_FORMAT
                return null
            }
        }

        // Para los CPrimitives no puede llegar un valor multiple, el multiple debe ser en el nivel mas bajo un ELEMENT
        throw new Exception("bindCInteger: Colección de pathValor tiene mas de un elemento.")
        
    } // bindCInteger

    def bindCReal(CReal cr, LinkedHashMap<String, Object> pathValorCReal, Archetype arquetipo, String tempId)
    {
        throw new Exception("bindCReal: Esta funcion no se deberia llamar ya que no deberian haber valores reales fuera de un quantity.")
    }

    def bindCString(CString cs, LinkedHashMap<String, Object> pathValorCString, Archetype arquetipo, String tempId)
    {
        println "==== bindCString"
        println "   = pathValor: " + pathValorCString
        println "   = tipos de valores: "
        pathValorCString.each {
            print it.value.getClass().toString() + ", "
        }
        println ""
        println "==========================================="
        
        if (pathValorCString.size() == 0)
        {
            return null // No hay valores para este nodo
        }
        if (pathValorCString.size() == 1)
        {
            //return new DvText(value: pathValorCString.find{true}.value)
            return new String(pathValorCString.find{true}.value)
        }
        
        throw new Exception("bindCString: Colección de pathValor tiene mas de un elemento.")
    }

    def bindCTime(CTime ct, LinkedHashMap<String, Object> pathValorCTime, Archetype arquetipo, String tempId)
    {
        // Encuentro los valores en la coleccion de path valor
        String hour = pathValorCTime.find{it.key.endsWith("hour")}?.value
        String minute = pathValorCTime.find{it.key.endsWith("minute")}?.value
        String seg = pathValorCTime.find{it.key.endsWith("seg")}?.value

        if (hour != null){
            // Creo un string con formato ISO 8601
            // FIXME: o usamos o metodos estaticos o el singleton para no tener 2 criterios distintos para hacer la misma cosa.
            String fechaISO8601 = FactoryObjectRM.crearFechaISO8601("", "", "", hour, minute, seg)
            return fechaISO8601
        }
        else{
            return null
            //throw new Exception("bindCTime: Colección de pathValor no tiene path a 'hora'.")
        }
    }

    //-----------------------------------------------------------------------------
    // CDomainType
    // Todos los bindeos de los tres tipos de CDomineType tienen el control de obligatoriedad
    //
    // PAB: ahora tira una lista de Ordinals con mas de un elemento si vienen valores multiples.
    //
    def bindCDvOrdinal(CDvOrdinal cdvo, LinkedHashMap<String, Object> pathValorCDvOrdinal, Archetype arquetipo, String tempId)
    {
        println "==== bindCDvOrdinal"
        println "   = pathValor: " + pathValorCDvOrdinal
        println "   = tipos de valores: "
        pathValorCDvOrdinal.each {
            print it.value.getClass().toString() + ", "
        }
        println ""
        println "==========================================="
        
        def min = cdvo.occurrences.getLower()

        if (pathValorCDvOrdinal.size() == 0)
        {
            // La verificacion de ocurrencias la debe hacer el element.
            //if (min > 0) listaErrores.add( fullPath(arquetipo,cdvo) )
            return null
        }
        
        // Size puede ser 1 pero puedo tener multiples valores....
        if (pathValorCDvOrdinal.size() == 1)
        {
            // ERROR: en a1_a2 me da un array no un string aca porque hay un CDvOrdinal multiple.
            //String v = pathValorCDvOrdinal.find{it.key.endsWith("value")}?.value
            def result = []
            //def values = pathValorCDvOrdinal.find{it.key.endsWith("value")}?.value // la path de CDvOrdinal termina con /value
            // Busqueda mas sencilla, porque si estoy en este metodo es que la path me corresponde, no es necesario verificar que
            // termina con 'value' (a no ser que quien me llame no haga la verificacion! => TODO: verificar esto).
            def values = pathValorCDvOrdinal.find{true}?.value // la path de CDvOrdinal termina con /value
            if (values.getClass().isArray()) // Valores multiples
            {
                (values as List).each { value ->
                
                    println " //////////////////////////////// VALUE 1: '" + value +"'" 
                
                    // FIXME: corregir chequeo de ocurrencias para multiples valores como esta en bindCComplexObject caso SA.
                    
                    if (!value) // Si es vacio o null
                    {
                        // FIXME: como hay multiples valores para la path, si el valor es requerido para cada instancia,
                        //        este error deberia aparecer una vez por cada uno de los valores que vinieron vacios,
                        //        de este modo un error sobreescribe el otro y si hay varios solo se registra uno y no
                        //        se puede distinguir cual. Podria hacerse en la vuelta a guigen, viendo si es que hay
                        //        multiples valores y un solo error, verificar cada valor vacio en params, para la path,
                        //        y mostrar el error para cada valor vacio.
                        //        Otra forma seria aqui registrar el error y el indice del valor para el cual dio error,
                        //        asi si vienen 5 valores y 2 son vacios, registro que el 2 y el 4 son los que estan vacios.
                        if (min > 0) listaErrores.add( fullPath(arquetipo,cdvo) )
                    }
                    else
                        result << rmFactory.createDvOrdinal(cdvo, value, arquetipo, arquetipo.node(cdvo.path()).nodeID, tempId)
                }
            }
            else // Valor simple
            {
                println " //////////////////////////////// VALUE 2: '" + values + "'" 
                
                if (!values) // Si es vacio o null
                {
                    if (min > 0) listaErrores.add( fullPath(arquetipo,cdvo) )
                }
                else
                    result << rmFactory.createDvOrdinal(cdvo, values, arquetipo, arquetipo.node(cdvo.path()).nodeID, tempId)
            }

            return result
        }

        throw new Exception("bindCDvOrdinal: Colección de pathValor tiene mas de una path.")
        
    } // bindCDvOrdinal

    def bindCDvQuantity(CDvQuantity cdvc, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindCDvQuantity"
        println "==== pathValor: " + pathValor
        
        def result = []
        
        // Valores potencialmente multiples
        def magnitudes
        def unidades
        def pathMagnitudes
        def pathUnidades

        def min = cdvc.occurrences.getLower()
        
        // FIXME: no soporta multiples ocurrencias! (hacer parecida a la recorrida de valores multiples de varias paths en CLUSTER).

        /* los errores de ocurrencias se verifican en ELEMENT
        if (pathValorCDvQuantity.size() == 0){
            if (min > 0){ 
                listaErrores.add( fullPath(arquetipo,cdvc) )
            }
            return null
        }
        */
        if ((pathValor.size() == 1) || (pathValor.size() == 2))
        {
            if (pathValor.size() == 1) // Si el Quantity tiene una sola unidad, esta no viene pathValorCDvQuantity, y hay que obtenerla de la definicion del arquetipo
            {
                magnitudes = pathValor.find{it.key.endsWith("magnitude")}?.value
                pathMagnitudes = pathValor.find{it.key.endsWith("magnitude")}?.key
                
                List<CDvQuantityItem> listQI = cdvc.list
                
                // LEA: Un quantity siempre debe tener unidad, pero se puede definir un quantity sin unidad.
                // PAB: Tomamos como convencion que siempre definimos en el arquetipo la unidad.
                //
                if (listQI != null)
                { 
                    if (listQI.size()==1) // Obtengo la unica unidad del arquetipo
                        unidades = listQI[0].units
                    else
                        throw new Exception("No viene la unidad de la web y el arquetipo "+ arquetipo.archetypeId.value +" define muchas unidades para CDvQuantity: "+ cdvc.path())
                }
                else
                {
                    // FIXME: siempre debe ahber unidad
                    //unidad = ""
                    throw new Exception("No se definio la unidad de CDvQuantity "+ cdvc.path() +" en arquetipo: " + arquetipo.archetypeId.value)
                }
            }
            else if (pathValor.size() == 2)
            {
                magnitudes = pathValor.find{it.key.endsWith("magnitude")}?.value
                pathMagnitudes = pathValor.find{it.key.endsWith("magnitude")}?.key
                unidades = pathValor.find{it.key.endsWith("units")}?.value
                pathUnidades = pathValor.find{it.key.endsWith("units")}?.key
            }

            if (magnitudes && unidades)
            {
                // Si hay valores multiples
                if (magnitudes.getClass().isArray() && unidades.getClass().isArray())
                {
                    //println "???????????? MAGNITUDES y UNIDADES MULTIPLES"
                    
                    def index = 0 // Para iterar por los valores multiples
                    def termino = false
                    while (!termino)
                    {
                        def magnitud
                        def unidad

                        pathValor.each { entry ->
                            
                            if (index < magnitudes.length) // ambos arrays deberian tener el mismo largo
                            {
                                magnitud = magnitudes[index]
                                unidad = unidades[index]
                            }
                        }
                        
                        // Si no tengo mas valores para bindear, termino...
                        termino = (!magnitud && !unidad)
                        
                        if (!termino) // en la ultima vuelta me aseguro de no bindear nada...
                        {
                            try
                            {
                                result << rmFactory.createDvQuantity(Double.parseDouble(magnitud), unidad, arquetipo, cdvc.nodeID, tempId)
                            }
                            catch(Exception e)
                            {
                                def path = pathValor.keySet().toArray()[0]
                                def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                                if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                                errors2[fullPath] << ERROR_BAD_FORMAT
                            }
                            
                            index++
                        }
                        
                    } // while ! termino
                }
                else if (magnitudes.getClass().isArray() && unidades instanceof String) // Si hay varias magnitudes pero una sola unidad.
                {
                    //println "???????????? MAGNITUDES MULTIPLES"
                            
                    def unidad = unidades // es un valor simple
                    (magnitudes as List).each { magnitud ->
                    
                        try
                        {
                            result << rmFactory.createDvQuantity(Double.parseDouble(magnitud), unidad, arquetipo, cdvc.nodeID, tempId)
                        }
                        catch(Exception e)
                        {
                            def path = pathValor.keySet().toArray()[0]
                            def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                            if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                            errors2[fullPath] << ERROR_BAD_FORMAT
                        }
                    }
                }
                else if (magnitudes instanceof String && unidades instanceof String) // Si ambos son simples
                {
                    //println "???????????? AMBOS VALORES SIMPLES"
                    def magnitud = magnitudes
                    def unidad = unidades
                    
                    try
                    {
                        result << rmFactory.createDvQuantity(Double.parseDouble(magnitud), unidad, arquetipo, cdvc.nodeID, tempId)
                    }
                    catch(Exception e)
                    {
                        def path = pathValor.keySet().toArray()[0]
                        def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                        if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                        errors2[fullPath] << ERROR_BAD_FORMAT
                    }
                }
                // No hay otro caso posible
            }
        }
        else
        {
            throw new Exception("Error en bindCDvQuantity, hay mas de 2 paths para este nodo...")
        }
        
        return result
        
    } // bindCDvQuantity

    def bindCCodePhrase(CCodePhrase ccp, LinkedHashMap<String, Object> pathValorCCodePhrase, Archetype arquetipo, String tempId)
    {
        println "==== bindCCodePhrase"
        println "   = pathValor: " + pathValorCCodePhrase
        println "   = tipos de valores: "
        pathValorCCodePhrase.each {
            print it.value.getClass().toString() + ", "
        }
        println ""
        println "==========================================="
        
        def min = ccp.occurrences.getLower()

        
        // FIXME: verificar ocurrencias como en CComplexObject.
        if (pathValorCCodePhrase.size() == 0)
        {
            if (min > 0) listaErrores.add( fullPath(arquetipo,ccp) )
            return null
        }
        
//      Size puede ser 1 pero puedo tener multiples valores....
        if (pathValorCCodePhrase.size() == 1)
        {
            def result = []
            def values = pathValorCCodePhrase.find{it.key.endsWith("defining_code")}?.value
            if (values.getClass().isArray()) // Valores multiples
            {
                (values as List).each { value ->
                
                    println " //////////////////////////////// VALUE multiple: '" + value +"'" 
            
                    if (value)
                        result << rmFactory.createCodePhrase(ccp, value, arquetipo, ccp.nodeID, tempId)
                        
                    /*
                     *  la verificacion de ocurrencias la debe hacer el element
                     */
                }
            }
            else // Valor simple
            {
                println " //////////////////////////////// VALUE simple: '" + values + "'" 
                
                if (values)
                    result << rmFactory.createCodePhrase(ccp, values, arquetipo, ccp.nodeID, tempId)
                    
                /*
                 *  la verificacion de ocurrencias la debe hacer el element
                 */
            }
            
            return result
        }

        throw new Exception("bindCCodePhrase: Colección de pathValor tiene mas de una path.")
        
    } // bindCCodePhrase

    //-----------------------------------------------------------------------------

    void imprimirObjetoXML(Object o){
        println "-----------------"
        XStream xstream = new XStream();
        String xml = xstream.toXML(o);
        println xml
        println "-----------------"
    }
    
    // =============================================================
    // ===================== Bindeo de representation.Item ========= 
    // =============================================================
    def bindCLUSTER(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindCLUSTER"
        println "==== pathValor: " + pathValor
        println "============================================================="
        def result = [] // lista de clusters
        
        if (cco.occurrences.isUpperUnbounded() || cco.occurrences.getUpper() > 1) // Si pueden haber muchas ocurrencias del cluster
        {
            // Si viene un solo valor para cada path es como el caso en que el cluster no tiene ocurrencias multiples
            
            if (pathValor.every{it.value instanceof String}) // Todos los valores son simples, copio el codigo de abajo...
            {
                println "MULTIPLES OCURRENCIAS, PERO NO HAY VALORES MULTIPLES"
                result << bindSingleCluster(cco, pathValor, arquetipo, tempId)
                
                /*
                // Lista de lista de objetos bindeados para cada atributo del CCObject
                List<Object> listaListRMO = []

                // El unico atributo que espero que tenga es 'items'
                cco.getAttributes().each { cattr ->
                
                    // Me quedo con los path que corresponde al atributo que se esta procesando
                    LinkedHashMap<String, Object> pathValorAtribute = pathValor.findAll{it.key.startsWith(cattr.path())}
                    def listRMO = bindAttribute(cattr, pathValorAtribute, arquetipo, tempId)
                    listaListRMO.add(listRMO)
                }
                
                // TODO: que pasa si no bindeo nada para items?
                
                if (listaListRMO.size()==1) // Ha bindeado 'items'
                {
                    def listaItems = listaListRMO[0] // Puede ser una lista de objetos
                    def cluster = rmFactory.createCLUSTER(listaItems, arquetipo, arquetipo.node(cco.path()).nodeID, tempId)
                    result << cluster
                }
                    
                // Cluster nunca puede tener 2 atributos bindeados
                */
               
            }
            else // Hay por lo menos una path que tiene multiples valores
            {
                println "MULTIPLES OCURRENCIAS, Y HAY VALORES MULTIPLES"
                
                // El criterio es "mandar" de a UN valor de las path multiples para crear UN Cluster para
                // cada "mandada", pero si el valor de una path es simple, solo se "manda" para el primer
                // cluster o sea:
                // [p1 => v1, p2 => [v21, v22], p3 => [v31, v32, v33] ]
                // Para crear el primer cluster uso subPathValor:  [p1=>v1, p2=>v21, p3=>v31]
                // Para crear el segundo cluster uso subPathValor: [p2=>v22, p3=>v32]
                // Para crear el tercer cluster uso subPathValor:  [p3=>v33]
                
                // WARNING: ESTO NO SOPORTA CLUSTERs multiples y ELEMENTs multiples, porque los elements
                //          serian siempre bindeados simples (se pasa de a un valor para cada path).
                //          Para soportar esto, las paths deberian venir diferenciadas para cada CLUSTER,
                //          por ejemplo poniendo un _1, _2, etc para cada repeticios del cluster.
                
                def index = 0 // Para iterar por los valores multiples
                def termino = false
                while (!termino)
                {
                    def subPathValor = [:]
                    
                    // armo el subPathValor
                    pathValor.each { entry ->
                        
                        if (entry.value.getClass().isArray()) // valores multiples
                        {
                            if (index < entry.value.length)
                                subPathValor[entry.key] = entry.value[index] // path => valor simple
                        }
                        else // supongo que el valor es un string, TODO: ver si existe algun caso que caiga aca!
                        {
                            // TODO: verificar si esto puede ser un problema: como se que el objeto simple
                            //       se corresponde con el primer cluster? no se... y si los valores del
                            //       cluster tienen una relacion semantica, aca estoy metiendo la pata.
                            if (index == 0) // solo se pone en el primer cluster
                            {
                                subPathValor[entry.key] = entry.value // path => valor simple
                            }
                        }
                    }
                    
                    // Si no tengo mas valores para bindear, termino...
                    termino = subPathValor.size() == 0
                    
                    if (!termino) // en la ultima vuelta me aseguro de no bindear nada...
                    {
                        // Bindea UN cluster
                        result << bindSingleCluster(cco, subPathValor, arquetipo, tempId)
                    
                        index++
                    }
                    
                } // while ! termino
            } // multiples valores
        }
        else // Cluster no tiene ocurrencias multiples
        {
            println "OCURRENCIA SIMPLE"
            /*
            // Lista de lista de objetos bindeados para cada atributo del CCObject
            List<Object> listaListRMO = []

            // El unico atributo que espero que tenga es 'items'
            cco.getAttributes().each { cattr ->
            
                // Me quedo con los path que corresponde al atributo que se esta procesando
                LinkedHashMap<String, Object> pathValorAtribute = pathValor.findAll{it.key.startsWith(cattr.path())}
                def listRMO = bindAttribute(cattr, pathValorAtribute, arquetipo, tempId)
                listaListRMO.add(listRMO)
            }
            
            // TODO: que pasa si no bindeo nada para items?
            
            if (listaListRMO.size()==1) // Ha bindeado 'items'
            {
                def listaItems = listaListRMO[0] // Puede ser una lista de objetos
                def cluster = rmFactory.createCLUSTER(listaItems, arquetipo, arquetipo.node(cco.path()).nodeID, tempId)
                result << cluster
            }
                
            // Cluster nunca puede tener 2 atributos bindeados
            */
            
            result << bindSingleCluster(cco, pathValor, arquetipo, tempId)
        }
        
        println "???????????????????????????????????? RETURN bindCLUSTER"
        
        return result
        
    } // bindCLUSTER
    
    // Metodo auxiliar para bindCLUSTER, devuelve un CLUSTER con sus ELEMENTs.
    def bindSingleCluster(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        def cluster = null
        
        // Lista de lista de objetos bindeados para cada atributo del CCObject
        List<Object> listaListRMO = []

        // El unico atributo que espero que tenga es 'items'
        cco.getAttributes().each { cattr ->
        
            // Me quedo con los path que corresponde al atributo que se esta procesando
            LinkedHashMap<String, Object> pathValorAtribute = pathValor.findAll{it.key.startsWith(cattr.path())}
            def listRMO = bindAttribute(cattr, pathValorAtribute, arquetipo, tempId)
            listaListRMO.add(listRMO)
        }
        
        println "|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
        println "|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
        println "|||||||||||||| listaListRMO: " + listaListRMO
        println "|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
        println "|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"
        
        // TODO: que pasa si no bindeo nada para items?
        
        if (listaListRMO.size()==1) // Ha bindeado 'items'
        {
            def listaItems = listaListRMO[0] // Puede ser una lista de objetos

            if (countValues(listaItems) > 0)
            {
                println "|||||||||||=========>>>>>>> (List<Object>?) tipo listaItems: " + listaItems.getClass()
                //createCLUSTER(List<Object> listaItems, Archetype arquetipo, String archNodeId, String tempId)
                cluster = rmFactory.createCLUSTER(listaItems, arquetipo, cco.nodeID, tempId)
            }
        }
            
        // Cluster nunca puede tener 2 atributos bindeados
        
        return cluster
    }
    
    
    // =============================================================
    // ===================== Bindeo de DataValues ==================
    // =============================================================
    def bindDV_MULTIMEDIA(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindDV_MULTIMEDIA"
        println "==== pathValor: " + pathValor
        println "=============================================="
        
        def result = []
        
        if (pathValor.size()==0) // Si no hay path ni valores
        {
            return result
        }
        if (pathValor.size()==1) // Si viene una path (serian solo archivos)
        {
            def key = pathValor.find{true}.key // agarro la unica entrada y pido su clave
            if (!pathValor[key]) // Si vino la path pero el valor es vacio
            {
                return result
            }
            
            // pueden haber muchos archivos
            if (pathValor[key].getClass().isArray()) // hay varios archivos
            {
                (pathValor[key] as List).each { file ->
                    if (!file.isEmpty())
                        result << rmFactory.createDV_MULTIMEDIA( file, arquetipo, cco.nodeID, tempId)
                }
            }
            else // hay un solo archivo
            {
                def file = pathValor[key]
                if (!file.isEmpty())
                    result << rmFactory.createDV_MULTIMEDIA( file, arquetipo, cco.nodeID, tempId)
            }
            
            return result
        }
        // por ahora no hay un caso donde venga archivo y otro dato.
        throw new Exception("Hay mas de una path para bindDV_MULTIMEDIA, hay: " + pathValor.size())
        
    } // bindDV_MULTIMEDIA
    
    def bindDV_BOOLEAN(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindDV_BOOLEAN"
        println "==== pathValor: " + pathValor
        println "=============================================="
        
        def result = []
        
        if (pathValor.size()==0) // Si no hay path ni valores
        {
            return result
        }
        if (pathValor.size()==1) // Si viene una path
        {
            def key = pathValor.find{true}.key // agarro la unica entrada y pido su clave
            
            // Los errores de ocurrencias las debe verificar el ELEMENT al que pertenece este DataValue.
            
            // pueden haber muchos archivos
            if (pathValor[key].getClass().isArray()) // hay varios archivos
            {
                (pathValor[key] as List).each { value ->
                
                    // el valor es string, es exacto lo que viene de la web
                    if (value)
                    {
                        try // chequeo de formato
                        {
                           def boolValue = Boolean.parseBoolean(value) // puede tirar except
                           result << rmFactory.createDV_BOOLEAN( boolValue, arquetipo, cco.nodeID, tempId)
                        }
                        catch(Exception e)
                        {
                           def path = pathValor.keySet().toArray()[0]
                           def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                           if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                           errors2[fullPath] << ERROR_BAD_FORMAT
                           //return null
                        }
                    }
                }
            }
            else // hay un solo archivo
            {
                def value = pathValor[key] // el valor es string, es exacto lo que viene de la web
                if (value)
                {
                    try // chequeo de formato
                    {
                        def boolValue = Boolean.parseBoolean(value) // puede tirar except
                        result << rmFactory.createDV_BOOLEAN( boolValue, arquetipo, cco.nodeID, tempId)
                    }
                    catch(Exception e)
                    {
                       def path = pathValor.keySet().toArray()[0]
                       def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                       if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                       errors2[fullPath] << ERROR_BAD_FORMAT
                       //return null
                    }
                }
            }
            
            return result // puede ser vacia
        }
        // por ahora no hay un caso donde venga el valor bool y otro dato.
        throw new Exception("Hay mas de una path para bindDV_BOOLEAN, hay: " + pathValor.size())
        
    } // bindDV_BOOLEAN
    
    def bindDV_TEXT(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindDV_TEXT"
        println "==== pathValor: " + pathValor
        println "=============================================="
        
        def result = []
        
        if (pathValor.size()==0) // Si no hay path ni valores
        {
            return result
        }
        if (pathValor.size()==1) // Si viene una path
        {
            def key = pathValor.find{true}.key // agarro la unica entrada y pido su clave
            
            // Los errores de ocurrencias las debe verificar el ELEMENT al que pertenece este DataValue.
            
            // pueden haber muchos archivos
            if (pathValor[key].getClass().isArray()) // hay varios archivos
            {
                (pathValor[key] as List).each { value ->
                
                    // el valor es string, es exacto lo que viene de la web
                    if (value)
                    {
                        result << rmFactory.createDV_TEXT( value, arquetipo, cco.nodeID, tempId)
                    }
                }
            }
            else // hay un solo archivo
            {
                def value = pathValor[key] // el valor es string, es exacto lo que viene de la web
                if (value)
                {
                    result << rmFactory.createDV_TEXT( value, arquetipo, cco.nodeID, tempId)
                }
            }
            
            return result // puede ser vacia
        }
        // por ahora no hay un caso donde venga el valor y otro dato.
        throw new Exception("Hay mas de una path para bindDV_TEXT, hay: " + pathValor.size())
    
    } // bindDV_TEXT
    
    
    def bindDV_COUNT(CComplexObject cco, LinkedHashMap<String, Object> pathValor, Archetype arquetipo, String tempId)
    {
        println "== bindDV_COUNT"
        println "==== pathValor: " + pathValor
        println "=============================================="
        
        def result = []
        
        if (pathValor.size()==0) // Si no hay path ni valores
        {
            return result
        }
        if (pathValor.size()==1) // Si viene una path
        {
            def key = pathValor.find{true}.key // agarro la unica entrada y pido su clave
            
            // Los errores de ocurrencias las debe verificar el ELEMENT al que pertenece este DataValue.
            
            // pueden haber muchos archivos
            if (pathValor[key].getClass().isArray()) // hay varios archivos
            {
                (pathValor[key] as List).each { value ->
                
                    // el valor es string, es exacto lo que viene de la web
                    if (value)
                    {
                        try // chequeo de formato
                        {
                            def intValue = Integer.parseInt(value)
                            result << rmFactory.createDV_COUNT( intValue, arquetipo, cco.nodeID, tempId)
                        }
                        catch(Exception e)
                        {
                           def path = pathValor.keySet().toArray()[0]
                           def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                           if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                           errors2[fullPath] << ERROR_BAD_FORMAT
                           //return null
                        }
                    }
                }
            }
            else // hay un solo archivo
            {
                def value = pathValor[key] // el valor es string, es exacto lo que viene de la web
                if (value)
                {
                    try // chequeo de formato
                    {
                        def intValue = Integer.parseInt(value)
                        result << rmFactory.createDV_COUNT( intValue, arquetipo, cco.nodeID, tempId)
                    }
                    catch(Exception e)
                    {
                       def path = pathValor.keySet().toArray()[0]
                       def fullPath = arquetipo.archetypeId.value + path // No puede llamar a fullPath() porque ci no es ArchetypeConstraint!
                       if ( !errors2[fullPath] ) errors2[fullPath] = [] // Creo lista de errores para esta path si no estaba creada
                       errors2[fullPath] << ERROR_BAD_FORMAT
                       //return null
                    }
                }
            }
            
            return result // puede ser vacia
        }
        // por ahora no hay un caso donde venga el valor y otro dato.
        throw new Exception("Hay mas de una path para bindDV_COUNT, hay: " + pathValor.size())
    
    } // bindDV_COUNT
}

